package dbrouter

import (
	"context"
	"fmt"
	"sync"
)

//Instancer ...
type Instancer interface {
	Close() error
	Reload() error
}

//FactoryFunc ...
type factoryFunc func(ctx context.Context, instance string) (in Instancer, err error)

//InstanceManager 实例管理器
type InstanceManager struct {
	instanceMu sync.RWMutex
	instances  map[string]Instancer
	factory    factoryFunc
}

//NewInstanceManager ...
func NewInstanceManager(factory factoryFunc) *InstanceManager {
	instanceManager := &InstanceManager{
		instances: make(map[string]Instancer),
		factory:   factory,
	}
	return instanceManager
}

//Get 获取实例
func (m *InstanceManager) Get(ctx context.Context, instance string) Instancer {
	fun := "InstanceManager.Get -->"
	var err error
	var in Instancer
	in, ok := m.GetInstance(ctx, instance)
	if ok == false {
		in, err = m.buildInstance(ctx, instance)
		if err != nil {
			fmt.Printf("%s NewInstance err, instance: %s, err: %s\n", fun, instance, err.Error())
			return nil
		}
	}

	return in
}

//Close ...
func (m *InstanceManager) Close() {
	m.instanceMu.Lock()
	defer m.instanceMu.Unlock()

	for key, in := range m.instances {
		go in.Close()
		delete(m.instances, key)
	}
}

func (m *InstanceManager) buildInstance(ctx context.Context, instance string) (Instancer, error) {
	m.instanceMu.Lock()
	defer m.instanceMu.Unlock()

	if in, ok := m.instances[instance]; ok {
		return in, nil
	}

	in, err := m.factory(ctx, instance)
	if err != nil {
		return nil, err
	}

	m.instances[instance] = in
	return in, nil
}

func (m *InstanceManager) CloseDbInstance(ctx context.Context, instance string) {
	fun := "InstanceManager.CloseDbInstance -->"

	m.instanceMu.Lock()
	defer m.instanceMu.Unlock()

	if in, ok := m.instances[instance]; ok {
		delete(m.instances, instance)
		go func() {
			if err := in.Close(); err == nil {
				fmt.Printf("%s succeed to close db instance: %s \n", fun)
			} else {
				fmt.Printf("%s close db instance: %s  error: %s\n", fun, instance, err.Error())
			}
		}()
	}
}

func (m *InstanceManager) ReloadDbInstance(ctx context.Context, instance string) {
	fun := "InstanceManager.ReloadDbInstance -->"
	m.instanceMu.Lock()
	defer m.instanceMu.Unlock()
	if in, ok := m.instances[instance]; ok {
		go func() {
			if err := in.Reload(); err == nil {
				fmt.Printf("%s succeed to reload db instance: %s \n", fun, instance)
			} else {
				fmt.Printf("%s reload db instance: %s  error: %s\n", fun, instance, err.Error())
			}
		}()
	}
}

func (m *InstanceManager) GetInstance(ctx context.Context, key string) (Instancer, bool) {
	m.instanceMu.RLock()
	defer m.instanceMu.RUnlock()

	in, ok := m.instances[key]
	return in, ok
}
